
#include "TrafficActorHotAreaRedisViewer.hpp"
#include "cybertron/node/TrafficActorHotArea.hpp"
#include "nlohmann/json.hpp"
#include "Node/HotArea.pb.h"
#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>

#ifdef CYBERTRON_WIN
#pragma warning(disable:4200) //zero-sized array in struct/union
#include "hiredis/sockcompat.h"
#include "hiredis/hiredis.h"
#endif
#undef GetMessage

typedef redisContext * (*FuncRedisConnectWithTimeout)(const char *ip, int port, const struct timeval tv);
typedef void (*FuncRedisFree)(redisContext *c);
typedef void * (*FuncRedisCommand)(redisContext *c, const char *format, ...);
typedef void (*FuncFreeReplyObject)(void *reply);

#define KEY_HOT_AREA "hotArea"
#define KEY_ACTOR "actor"
using namespace ::google::protobuf;
static FuncRedisConnectWithTimeout funcRedisConnectWithTimeout = nullptr;
static FuncRedisFree funcRedisFree = nullptr;
static FuncRedisCommand funcRedisCommand = nullptr;
static FuncFreeReplyObject funcFreeReplyObject = nullptr;
static bool hRedisInitilized = false;
CYBERTRON_BEGIN

static void FormatToJson(nlohmann::json& _json, const std::string& name, const ::google::protobuf::Message& msg)
{
	const Descriptor* descriptor = msg.GetDescriptor();
	const Reflection* reflection = msg.GetReflection();

	_json[name] = nlohmann::json::object();
	nlohmann::json& json = _json[name];

	const int count = descriptor->field_count();

	for (int i = 0; i < count; ++i)
	{
		const FieldDescriptor* field = descriptor->field(i);
		if (!field)
		{
			continue;
		}
		bool repeated = field->is_repeated();
		int fieldSize = 0;
		nlohmann::json* pArr = nullptr;
		if (repeated)
		{
			fieldSize = reflection->FieldSize(msg, field);
			if (fieldSize == 0)
				continue;
			json[field->name()] = nlohmann::json::array();
			pArr = &json[field->name()];
		}
		else
		{
			if (!reflection->HasField(msg, field))
				continue;
		}
		
		switch (field->type())
		{
		case FieldDescriptor::TYPE_DOUBLE:
			if (repeated)
			{
				for (int i = 0; i < fieldSize; i++)
				{
					pArr->push_back(reflection->GetRepeatedDouble(msg, field, i));
				}
			}
			else
			{
				json[field->name()] = reflection->GetDouble(msg, field);
			}
			break;
		case FieldDescriptor::TYPE_FLOAT:
			if (repeated)
			{
				for (int i = 0; i < fieldSize; i++)
				{
					pArr->push_back(reflection->GetRepeatedFloat(msg, field, i));
				}
			}
			else
			{
				json[field->name()] = reflection->GetFloat(msg, field);
			}
			break;
		case FieldDescriptor::TYPE_INT64:
		case FieldDescriptor::TYPE_FIXED64:
		case FieldDescriptor::TYPE_SFIXED64:
		case FieldDescriptor::TYPE_SINT64:
			if (repeated)
			{
				static char int64str[25];
				for (int i = 0; i < fieldSize; i++)
				{
					memset(int64str, 0, sizeof(int64str));
					snprintf(int64str, sizeof(int64str), "%lld", (long long)reflection->GetRepeatedInt64(msg, field, i));
					pArr->push_back(int64str);
				}
			}
			else
			{
				static char int64str[25];
				memset(int64str, 0, sizeof(int64str));
				snprintf(int64str, sizeof(int64str), "%lld", (long long)reflection->GetInt64(msg, field));
				json[field->name()] = int64str;
			}
		break;
		case FieldDescriptor::TYPE_UINT64:
			if (repeated)
			{
				static char int64str[25];
				for (int i = 0; i < fieldSize; i++)
				{
					memset(int64str, 0, sizeof(int64str));
					snprintf(int64str, sizeof(int64str), "%lld", (long long)reflection->GetRepeatedUInt64(msg, field, i));
					pArr->push_back(int64str);
				}
			}
			else
			{
			static char uint64str[25];
			memset(uint64str, 0, sizeof(uint64str));
			snprintf(uint64str, sizeof(uint64str), "%llu", (unsigned long long)reflection->GetUInt64(msg, field));
			json[field->name()] = uint64str;
		}
		break;
		case FieldDescriptor::TYPE_INT32:
		case FieldDescriptor::TYPE_FIXED32:
		case FieldDescriptor::TYPE_SFIXED32:
		case FieldDescriptor::TYPE_SINT32:
			if (repeated)
			{
				for (int i = 0; i < fieldSize; i++)
				{
					pArr->push_back(reflection->GetRepeatedInt32(msg, field, i));
				}
			}
			else
			{
				json[field->name()] = reflection->GetInt32(msg, field);
			}
			break;
		case FieldDescriptor::TYPE_BOOL:
			if (repeated)
			{
				for (int i = 0; i < fieldSize; i++)
				{
					pArr->push_back(reflection->GetRepeatedBool(msg, field, i));
				}
			}
			else
			{
				json[field->name()] = reflection->GetBool(msg, field);
			}
			break;
		case FieldDescriptor::TYPE_STRING:
			if (repeated)
			{
				for (int i = 0; i < fieldSize; i++)
				{
					pArr->push_back(reflection->GetRepeatedString(msg, field, i));
				}
			}
			else
			{
				json[field->name()] = reflection->GetString(msg, field);
			}
			break;
		case FieldDescriptor::TYPE_GROUP: //Deprecated.
			break;
		case FieldDescriptor::TYPE_MESSAGE:
		{
			if (repeated)
			{
				for (int i = 0; i < fieldSize; i++)
				{
					const auto& tmp_msg = reflection->GetRepeatedMessage(msg, field, i);
					if (0 != tmp_msg.ByteSizeLong())
					{
						nlohmann::json tempJson;
						FormatToJson(tempJson, "message", tmp_msg);
						pArr->push_back(tempJson["message"]);
					}
				}
			}
			else
			{
				const auto& tmp_msg = reflection->GetMessage(msg, field);
				if (0 != tmp_msg.ByteSizeLong())
				{
					FormatToJson(json, field->name(), tmp_msg);
				}
			}
		}
		break;
		case FieldDescriptor::TYPE_BYTES:
			if (repeated)
			{
				for (int i = 0; i < fieldSize; i++)
				{
					pArr->push_back(reflection->GetRepeatedString(msg, field, i));
				}
			}
			else
			{
				json[field->name()] = reflection->GetString(msg, field);
			}
			break;
		case FieldDescriptor::TYPE_UINT32:
			if (repeated)
			{
				for (int i = 0; i < fieldSize; i++)
				{
					pArr->push_back(reflection->GetRepeatedUInt32(msg, field, i));
				}
			}
			else
			{
				json[field->name()] = reflection->GetUInt32(msg, field);
			}
			break;
		case FieldDescriptor::TYPE_ENUM:
		{
			if (repeated)
			{
				for (int i = 0; i < fieldSize; i++)
				{
					const auto enumDescriptor = reflection->GetRepeatedEnum(msg, field, i);
					if (enumDescriptor)
					{
						pArr->push_back(enumDescriptor->name());
					}
				}
			}
			else
			{
				const auto enumDescriptor = reflection->GetEnum(msg, field);
				if (enumDescriptor)
				{
					json[field->name()] = enumDescriptor->name();
				}
			}
		}
		break;
		default:
			break;
		}
	}
}

void TrafficActorHotAreaRedisViewer::Init(const std::string& ip, int port, int dbIndex)
{
	mRedisReady = false;
#ifdef CYBERTRON_WIN
	if(!hRedisInitilized)
	{
		HMODULE hInstLibrary = ::LoadLibraryA("hiredis.dll");
		if (!hInstLibrary) return;
		funcRedisConnectWithTimeout = (FuncRedisConnectWithTimeout)GetProcAddress(hInstLibrary, "redisConnectWithTimeout");
		funcRedisFree = (FuncRedisFree)GetProcAddress(hInstLibrary, "redisFree");
		funcRedisCommand = (FuncRedisCommand)GetProcAddress(hInstLibrary, "redisCommand");
		funcFreeReplyObject = (FuncFreeReplyObject)GetProcAddress(hInstLibrary, "freeReplyObject");
		if (!funcRedisConnectWithTimeout || !funcRedisFree || !funcRedisCommand || !funcFreeReplyObject)
		{
			funcRedisConnectWithTimeout = nullptr; funcRedisFree = nullptr; funcRedisCommand = nullptr;  funcFreeReplyObject=nullptr;
			FreeLibrary(hInstLibrary);
			return;
		}
		hRedisInitilized = true;
	}
	if (mRedisContenxt)
	{
		funcRedisFree(mRedisContenxt);
		mRedisContenxt = nullptr;
	}	
	struct timeval timeout = {1, 0}; // 1second
	mRedisContenxt = funcRedisConnectWithTimeout(ip.c_str(), port, timeout);
	if (!mRedisContenxt) {
		return;
	}
	if (mRedisContenxt->err)
	{
		funcRedisFree(mRedisContenxt);
		mRedisContenxt = nullptr;
		return;
	}
	auto reply = funcRedisCommand(mRedisContenxt, "select %d", dbIndex);
	if (!reply)
	{
		funcRedisFree(mRedisContenxt);
		mRedisContenxt = nullptr;
		return;
	}
	funcFreeReplyObject(reply);
	mRedisReady = true;
#endif
}

std::string TrafficActorHotAreaRedisViewer::GetHotAraKey(int hotAreaId)
{
	return std::string(KEY_HOT_AREA)+ std::to_string(hotAreaId);
}
std::string TrafficActorHotAreaRedisViewer::GetActorKey(int hotAreaId)
{
	return GetHotAraKey(hotAreaId) + KEY_ACTOR;
}

void TrafficActorHotAreaRedisViewer::RefreshAll(const TrafficActorHotArea* hotArea)
{
	if (mRedisReady)
	{
		Delete(GetHotAraKey(hotArea->getId()));
		Delete(GetActorKey(hotArea->getId()));
		UpdateHotArea(hotArea);
		for (size_t i = 0; i < hotArea->getNumActors(); i++)
		{
			const auto& actor = hotArea->getActor(i);
			UpdateActor(hotArea->getId(), &actor);
		}
	}
}
void TrafficActorHotAreaRedisViewer::UpdateHotArea(const TrafficActorHotArea* hotArea)
{
	if (mRedisReady)
	{
		Write(GetHotAraKey(hotArea->getId()), FormatHotArea(hotArea));
	}
}
void TrafficActorHotAreaRedisViewer::UpdateActor(int hotAreaId, const TrafficActor* actor)
{
	if (mRedisReady)
	{
		HashWrite(GetActorKey(hotAreaId), std::to_string(actor->getRuntimeId()), FormatTrafficActor(actor));
	}
}

std::string TrafficActorHotAreaRedisViewer::FormatHotArea(const TrafficActorHotArea* hotArea)
{
	static const char* JSON_FORMAT = R"({"id":%d, "pos":{"x":%.4f,"y":%.4f, "z":%.4f}, "radius":%.4f})";
	char buff[256];
	auto id = hotArea->getId();
	const auto& position = hotArea->getPosition();
	auto radius = hotArea->getRadius();
	snprintf(buff, 256, JSON_FORMAT, id, position.x, position.y, position.z, radius);
	return buff;
}
std::string TrafficActorHotAreaRedisViewer::FormatTrafficActor(const TrafficActor* actor)
{
	nlohmann::json json;
	json["actorID"] = actor->getRuntimeId();
	json["assetID"] = actor->getAssetId();
	json["pos"] = nlohmann::json::object();
	nlohmann::json& pos = json["pos"];
	const auto& position = actor->getWorldPosition();
	pos["x"] = position.x; pos["y"] = position.y; pos["z"] = position.z;
	json["rot"] = nlohmann::json::object();
	nlohmann::json& rot = json["rot"];
	const auto& rotation = actor->getWorldRotation();
	rot["x"] = rotation.x; rot["y"] = rotation.y; rot["z"] = rotation.z;
	json["size"] = nlohmann::json::object();
	nlohmann::json& size = json["size"];
	const auto& _size = actor->getObjectSpaceSize();
	size["x"] = _size.x; size["y"] = _size.y; size["z"] = _size.z;
	json["flag"] = actor->getFlags();
	json["type"] = actor->getActorType();
	std::string field;
	::google::protobuf::Message* message;
	switch (actor->getActorType())
	{
	case HotArea::EActorType::EActorType_MainVehicle:
		message = new HotArea::MainVehiclePrivateData();
		field = "data";
		break;
	case HotArea::EActorType::EActorType_TrafficVehicle:
		message = new HotArea::TrafficVehiclePrivateData();
		field = "data";
		break;
	case HotArea::EActorType::EActorType_TrafficLight:
		message = new HotArea::TrafficLightPrivateData();
		field = "data";
		break;
	case HotArea::EActorType::EActorType_Pedestrian:
		message = new HotArea::PedestrianPrivateData();
		field = "data";
		break;
	case HotArea::EActorType::EActorType_Bicycle:
		message = new HotArea::TwoWheelVehiclePrivateData();
		field = "data";
		break;
	case HotArea::EActorType::EActorType_Data:
		break;
	case HotArea::EActorType::EActorType_StaticObject:
		message = new HotArea::StaticObjectPrivateData();
		field = "data";
		break;
	}
	if (message && !field.empty())
	{
		if (actor->getCustomProperties(*message))
		{
			FormatToJson(json, field, *message);
		}
	}
	if (message) delete message;
	return std::move(json.dump());
}
void TrafficActorHotAreaRedisViewer::Write(const std::string& key, const std::string value)
{
	if (!mRedisContenxt || !mRedisReady) return;
#ifdef CYBERTRON_WIN
	auto reply = funcRedisCommand(mRedisContenxt, "set %s %s", key.c_str(), value.c_str());
	if(reply) funcFreeReplyObject(reply);
#endif
}
void TrafficActorHotAreaRedisViewer::HashWrite(const std::string& key, const std::string& hashKey, const std::string& value)
{
	if (!mRedisContenxt || !mRedisReady) return;
#ifdef CYBERTRON_WIN
	auto reply = funcRedisCommand(mRedisContenxt, "hset %s %s %s", key.c_str(), hashKey.c_str(), value.c_str());
	if (reply) funcFreeReplyObject(reply);
#endif
}
void TrafficActorHotAreaRedisViewer::HashDelete(const std::string& key, const std::string& hashKey)
{
	if (!mRedisContenxt || !mRedisReady) return;
#ifdef CYBERTRON_WIN
	auto reply = funcRedisCommand(mRedisContenxt, "hdel %s %s", key.c_str(), hashKey.c_str());
	if (reply) funcFreeReplyObject(reply);
#endif
}
void TrafficActorHotAreaRedisViewer::Delete(const std::string& key)
{
	if (!mRedisContenxt || !mRedisReady) return;
#ifdef CYBERTRON_WIN
	auto reply = funcRedisCommand(mRedisContenxt, "del %s", key.c_str());
	if (reply) funcFreeReplyObject(reply);
#endif
}
TrafficActorHotAreaRedisViewer::~TrafficActorHotAreaRedisViewer()
{
#ifdef CYBERTRON_WIN
	if(mRedisContenxt) funcRedisFree(mRedisContenxt);
	mRedisContenxt = nullptr;
#endif
}
CYBERTRON_END